/**
 * @Author: lena
 * @Description:
 * @Version: 1.0.0
 * @Date: 2021/8/27 13:42
 */

package keyword

import (
	"fmt"
	"math/rand"
	"runtime"
	"sync"
	"time"
)

// Goroutine1 开启协程
func Goroutine1() {
	go method10("first")
	go method10("two")
	go method10("three")
	i := 0
	// for{}代表死循环
	for {
		fmt.Println("main", i)
		i++
		// 为了观察让程序让出cpu，休眠一会儿再继续向下
		//time.Sleep(1*time.Second)
	}
}

func method10(name string) {
	i := 0
	// for{}代表死循环
	for {
		fmt.Println(name, ":", i)
		i++
		// 为了观察让程序让出cpu，休眠一会儿再继续向下
		//time.Sleep(1*time.Second)
	}
}

// Goroutine2 主程序死亡，其他协程也会退出
func Goroutine2() {
	go method10("first")
	go method10("two")
	go method10("three")
	fmt.Println("main over")
}

// Goroutine3 匿名函数
func Goroutine3() {
	go func() {
		// 死循环
		for {
			fmt.Println("goroutine")
			func() {
				fmt.Println("sun")
			}()
			time.Sleep(1 * time.Second)
		}
		// 在方法体后面加()表示自调用，如果方法有参数，还要输入参数
	}()
	for {
		time.Sleep(1 * time.Second)
		fmt.Println("main")
	}
}

// Goroutine4 退出程序:runtime.Goexit()
func Goroutine4() {
	go func() {
		// 死循环
		for {
			fmt.Println("start")
			// ①如果在此处使用retun,则会退出当前方法,即协程停止,只会输出一次start
			// return
			func() {
				// ②如果在此处使用return,只会退出当前方法,start和over继续输出
				// return
				fmt.Println("sun")
				// ③真正结束当前协程的执行,只会输出一次start和sun
				runtime.Goexit()
			}()
			time.Sleep(1 * time.Second)
			fmt.Println("over")
		}
		// 在方法体后面加()表示自调用，如果方法有参数，还要输入参数
	}()
	for {
		time.Sleep(1 * time.Second)
		fmt.Println("main")
	}
}

// Goroutine5 固定协程数量 执行未知数量的任务
func Goroutine5() {
	jobs := make(chan int, 10)
	result := make(chan int, 10)
	// 开启三个goroutine用于工作
	for w := 1; w <= 3; w++ {
		go worker(w, jobs, result)
	}
	// 制定任务
	for j := 1; j <= 10; j++ {
		jobs <- j // 任务存入jobs 在worker会感知任务的添加后执行
	}
	// 关闭通道
	close(jobs)
	// 打印结果
	for a := 1; a <= 5; a++ {
		fmt.Println("result:", <-result)
	}
}
func worker(id int, jobs <-chan int, result chan<- int) {
	for j := range jobs {
		fmt.Println(len(jobs))
		fmt.Println(id, ":", j, "start")
		time.Sleep(time.Second)
		fmt.Println(id, ":", j, "end")
		result <- j * 2
	}
}

// WaitGroup
var wg sync.WaitGroup

// Goroutine7 测试结论：若每个协程都有recover，那么某个协程panic了会被recover捕捉，不会导致全部协程崩溃。
func Goroutine7() {
	for i := 3; i > -1; i-- {
		wg.Add(1)
		go divided(3, i)
	}
	wg.Wait()
}

func divided(a, b int) {
	defer func() {
		err := recover()
		if err != nil {
			fmt.Println("err :", err)
		}
	}()
	for {
		time.Sleep(time.Second * 3)
		fmt.Printf("i am alive. %v/%v=%v\n", a, b, a/b)
	}
	wg.Done()
}

/**
题一：使用goroutine和channel实现一个计算int64随机数各位数和的程序
1. 开启一个goroutine循环生成int64类型的随机数，发送到channel中
2。 开启24个goroutine从channel中读出随机数，并计算各位和，将结果存入result_channel中
3. main从result_channel取出结果并打印到终端输出
*/

// Goroutine6 题一入口
func Goroutine6() {
	data := make(chan int64, 10)
	result := make(chan map[int64]int, 10)
	// 开启一个goroutine生成随机数
	go createData(data)
	// 开启24个goroutine计算各个数之和
	for i := 0; i < 24; i++ {
		go sumData(data, result)
	}
	// main取出结果
	for {
		res := <-result
		fmt.Println(res)
	}
}

// 生成随机数
func createData(data chan int64) {
	for {
		// 生成随机数
		i := rand.Int63()
		// 存入channel中
		data <- i
		fmt.Println("create data :", i)
	}
}

// 计算和
func sumData(data chan int64, result chan map[int64]int) {
	for {
		// 取出一个数
		cur := <-data
		// 计算和
		sum := 0
		for i := cur; i != 0; i /= 10 {
			//  178 178%10=8,178/10%10=7
			sum = sum + int(i%10)
		}
		// 将结果存入result_channel中
		result <- map[int64]int{
			cur: sum,
		}
		fmt.Println("sum ", cur, ":", sum)
	}
}
